#include "ConfigParser.h"
#include "AnalysisConfig.h"
#include <iostream>
#include <fstream>
#include <iterator>

#include <boost/tokenizer.hpp>
#include <boost/regex.hpp>

using namespace std;

vector <AnalysisConfig> ConfigParser::parseConfig(const string & filename) {
    vector <AnalysisConfig> V; ///< This vector is used in the construction process
    vector <AnalysisConfig> additional; ///< This vector is used to store included files
    V.push_back(AnalysisConfig());

    ifstream configfile(filename.c_str()); ///< The ifstream object we will use to read the config file
    istreambuf_iterator<char> file_iter(configfile); ///< The iterator we will use to actually read the file's contents
    istreambuf_iterator<char> end_of_file; ///< This is the iterator to compare against when checking if we've hit eof

    typedef boost::tokenizer <boost::char_separator<char>, istreambuf_iterator<char> > streamtokenizer;
    typedef boost::tokenizer <boost::char_separator<char> > stringtokenizer;

    streamtokenizer tokcolon(file_iter,end_of_file,boost::char_separator<char>(";"));
    ///< This tokenizer will divide the input into parts by ';', which will give us individual options.

    stringtokenizer tokequals(string(""),boost::char_separator<char>("="));
    /**< This tokenizer will split every option by '=', providing a (keys,values) pair.
      The string passed to the constructor is here only because I had to provide one,
      it will be reasigned before every use.
      */

    stringtokenizer tokkeys(string(""),boost::char_separator<char>(", \n\t\r\b"));
    stringtokenizer tokvalues(string(""),boost::char_separator<char>(", \n\t\r\b"));
    /**< These tokenizers split the strings into individual parts based on whitespace and punctuation.
      The string passed to the constructor is here only because I had to provide one,
      it will be reasigned before every use.
      */
    stringtokenizer tokfilenames(string(""),boost::char_separator<char>("+* \n\t\r\b"));
    /**< This tokenizer is used only while interpreting the > command. It seperates pathnames from 
    */

    for(streamtokenizer::iterator itcolon = tokcolon.begin();itcolon!=tokcolon.end();itcolon++) {
        cout << "Line: " << *itcolon << endl;
        if(boost::regex_match(*itcolon,boost::regex("\\s*"))) { 
            cout << "Whitespace\n";
            ///< If a line is entirely empty, ignore it.
            continue;
        }
        if(boost::regex_match(*itcolon,boost::regex("\\s*#.*"))) {
            cout << "Commentary\n";
            ///< If a line starts with a #, it is a commentary line, so ignore it
            continue;
        }
        if(boost::regex_match(*itcolon,boost::regex("\\s*\\+.*"))) { 
            /**< If a line matches the above pattern (starts with >), then we interpret this as an "include" request.
             * All subsequent words are treated as files to be parsed. Eached prased file's AnalysisConfig vector shall
             * be appended to this file's vector. WARNING: do not use colons or spaces in file names.
             */
            cout << "Execution\n";
            tokfilenames.assign(*itcolon);
            for(stringtokenizer::iterator itfilenames = tokfilenames.begin();itfilenames!=tokfilenames.end();itfilenames++) {
                vector <AnalysisConfig> temp = parseConfig(*itfilenames);
                additional.insert(additional.end(),temp.begin(),temp.end());
            }
            continue;
        }
        if(boost::regex_match(*itcolon,boost::regex("\\s*\\*.*"))) { 
            /**< If a line matches the above pattern (starts with >), then we interpret this as an "include" request.
             * All subsequent words are treated as files to be parsed. Eached prased file's AnalysisConfig vector shall
             * be appended to this file's vector. WARNING: do not use colons or spaces in file names.
             */
            cout << "Inclusion\n";
            tokfilenames.assign(*itcolon);
            for(stringtokenizer::iterator itfilenames = tokfilenames.begin();itfilenames!=tokfilenames.end();itfilenames++) {
                vector <AnalysisConfig> temp = parseConfig(*itfilenames);
                V = crossProduct(temp,V);
            }
            continue;
        }

        tokequals.assign(*itcolon); ///< Assigning the '=' parser to each "keys = values".

        stringtokenizer::iterator itequals = tokequals.begin();

        string keys=*itequals;
        tokkeys.assign(keys); ///< Assigning the whitespace parser to the keys

        itequals++;
        string values=*itequals;
        tokvalues.assign(values); ///< Assigning the whitespace parser to the values

        stringtokenizer::iterator itkeys=tokkeys.begin(), itvalues = tokvalues.begin();

        AnalysisConfig partial; ///< This variable will contain individual key-value mappings for the option we are currently parsing.
        vector <AnalysisConfig> partialVector;
        do {
            itkeys = tokkeys.begin();
            while(itkeys!=tokkeys.end()) {
                if(itvalues==tokvalues.end()) {
                    cerr << "Ilość kluczy nie dzieli równo ilości wartości" << endl;
                }
                string a = *itkeys;
                string b = *itvalues;
                partial.setParameter(a,b);
                cout << "Setting: " << a << " na: " << b << endl;
                itvalues++;
                itkeys++;
            }
            /**< Partial now contains a specific key-value mapping for a subset of keys, now we must add this mapping to all elements
              of the current AnalysisConfiguration vector
              */
            partialVector.push_back(partial);
        } while(itvalues!=tokvalues.end());
        V = crossProduct(partialVector,V);
    }
    V.insert(V.end(),additional.begin(),additional.end());
    return V;
}

/** Calculates the cross product of two vectors.
 * @param primary - the more important set of parameters. If a variable is set in primary and secondary, the value in secondary will be overridden.
 * @param secondary - the less important set of parameters.
 **/
vector <AnalysisConfig> ConfigParser::crossProduct(vector <AnalysisConfig> primary, vector <AnalysisConfig> secondary) {
    vector <AnalysisConfig> crossProduct; ///< This vector will hold the result of the crossproduct we are calculating
    for(vector<AnalysisConfig>::iterator p = primary.begin();p!=primary.end();p++) {
        for(vector<AnalysisConfig>::iterator s = secondary.begin();s!=secondary.end();s++) {
            crossProduct.push_back(p->add(*s));
        }
    }
    return crossProduct;
}

vector <AnalysisConfig> ConfigParser::initialize(const std::vector <AnalysisConfig> & configvect) {
    vector <AnalysisConfig> newconfigvect = configvect;
    for(vector <AnalysisConfig>::iterator it = newconfigvect.begin();it!=newconfigvect.end();it++) {
        if(!it->getIsData()) it->setParameter("dataFileName",it->getMCTemplFileName());

        IOmanager& IOman = IOmanager::instance();
        IOman.openInput(it->getDataFileName());
        // Ostatni argument niepewny - nie wiem, czy to ma być pierwszy numer template, jesli jest ich wiele
        IOman.readAxisValuesFromFile(it->getDataFileName(),it->getSigProbeTemplate(1));

        int minBinET = IOman.getETbin(it->getETMin());
        int maxBinET = IOman.getETbin(it->getETMax()) - 1; // _ET_MAX_ is upper range and should not be added
        IOman.closeInput();

        // threshold for template building
        int minBinETFix=4;
        if( minBinET < minBinETFix ) minBinETFix=minBinET;

        if( it->getVersion() == 360 || it->getVersion() == 460 || it->getVersion() == 560 ) minBinETFix = minBinET;

        int templETAMin = it->getETAMin();
        int templETAMax = it->getETAMax();
        if(templETAMin == 5 || templETAMin == 14) {
            // in case of crack use average template
            templETAMin = 1;
            templETAMax = 18;
        }
        for(int templNumber = 1;templNumber<=it->getBgdProbeTemplNumber();templNumber++) {
            try {
                // Template with reverted cuts
                it->probeBgdTempl.push_back(HistogramConfig(it->getBgdProbeTemplate(templNumber),templETAMin,templETAMax,minBinETFix,maxBinET));
            } catch(...) {break;}
        }
        for(int templNumber = 1;templNumber<=it->getBgdSelectedTemplNumber();templNumber++) {
            try {
                // Template with reverted cuts
                it->selectedBgdTempl.push_back(HistogramConfig(it->getBgdSelectedTemplate(templNumber),templETAMin,templETAMax,minBinETFix,maxBinET));
            } catch(...) {break;}
        }
        for(int templNumber = 1;templNumber<=it->getSigProbeTemplNumber();templNumber++) {
            try {
                // Template with reverted cuts
                it->probeSigTempl.push_back(HistogramConfig(it->getSigProbeTemplate(templNumber),it->getETAMin(),it->getETAMax(),minBinET,maxBinET));
            } catch(...) {break;}
        }
        for(int templNumber = 1;templNumber<=it->getSigSelectedTemplNumber();templNumber++) {
            try {
                // Template with reverted cuts
                it->selectedSigTempl.push_back(HistogramConfig(it->getSigSelectedTemplate(templNumber),it->getETAMin(),it->getETAMax(),minBinET,maxBinET));
            } catch(...) {break;}
        }

        it->dataProbe = HistogramConfig(it->getDataProbe(),it->getETAMin(),it->getETAMax(),minBinET,maxBinET);
        it->dataSelected = HistogramConfig(it->getDataSelected(),it->getETAMin(),it->getETAMax(),minBinET,maxBinET);
    }
    return newconfigvect;
}
